package com.leyou.auth.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.auth.constants.RedisConstants;
import com.leyou.auth.dto.Payload;
import com.leyou.auth.dto.UserDetail;
import com.leyou.common.exception.LyException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.Charset;
import java.sql.Time;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


public class JwtUtils {

    /**
     * JWT解析器
     */
    private final JwtParser jwtParser;
    /**
     * 秘钥
     */
    private final SecretKey secretKey;

    //注入redis
    private final StringRedisTemplate redisTemplate;

    private final static ObjectMapper mapper = new ObjectMapper();



    public JwtUtils(String key,StringRedisTemplate redisTemplate) {

       this.redisTemplate = redisTemplate;

        // 生成秘钥
        secretKey = Keys.hmacShaKeyFor(key.getBytes(Charset.forName("UTF-8")));
        // JWT解析器
        this.jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
    }




    /**
     * 生成jwt，用默认的JTI
     * **jti**: jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击。
     * @param userDetails 用户信息
     * @return JWT
     */
    public String createJwt(UserDetail userDetails) {
        return createJwt(userDetails, 1800);
    }



    /**
     * 生成jwt，自己指定的JTI
     *
     * @param userDetails 用户信息
     * @return JWT
     */
    public String createJwt(UserDetail userDetails, int expireSeconds) {
        //随机生成一个JTI
        String jti = createJti();
        try {
            // 生成token
            String token = Jwts.builder().signWith(secretKey)
                    .setId(jti)
                    .claim("user", mapper.writeValueAsString(userDetails))
                    .setExpiration(DateTime.now().plusSeconds(expireSeconds).toDate())
                    .compact();

            //将jtl存储到Redis中
            //JTI_KEY_PREFIX 用户登录时记录TokenID的key前缀
            //public static final String JTI_KEY_PREFIX = "auth:login:uid:";
            this.redisTemplate.opsForValue()
                    .set(RedisConstants.JTI_KEY_PREFIX + userDetails.getId(), jti, 30, TimeUnit.MINUTES);

          return token;

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }




    /**
     * 解析jwt，并将用户信息转为指定的Clazz类型
     * @param jwt   token
     * @return 载荷，包含JTI和用户信息
     *
     * 如果在此做重置过期时间
     * 好处：业务合并，顺便做了过期时间刷新
     * 坏处：业务合并，意味着，用了token一定要用redis,视情况而定，耦合性强了，所以不建议
     *
     */
    public Payload parseJwt(String jwt) {
        try {
            Jws<Claims> claimsJws = jwtParser.parseClaimsJws(jwt);
            Claims claims = claimsJws.getBody();

            Payload payload = new Payload();
            payload.setJti(claims.getId());
            payload.setUserDetail(mapper.readValue(claims.get("user", String.class), UserDetail.class));

            //Redis中存储Jti的key
            String key = RedisConstants.JTI_KEY_PREFIX + payload.getUserDetail().getId();

            //将token中的jti取出做对比
            String jtl = redisTemplate.opsForValue().get(key);

            if (jtl == null) {
                throw new LyException("jti过期", 400);
            }

            if (!payload.getJti().equals(jtl)) {
                throw new LyException("你的账号在另一台服务器登录", 500);
            }

            //重置过期时间？

            return payload;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }




    /**
     * UUID随机生成JTI
     * @return
     */
    private String createJti() {
        return StringUtils.replace(UUID.randomUUID().toString(), "-", "");
    }
}
